package com.ice.framework.web.repeat;

import cn.hutool.core.util.ReflectUtil;
import com.ice.framework.common.exception.IdempotentException;
import com.ice.framework.common.util.MiscUtil;
import com.ice.framework.web.helper.RequestHelper;
import com.ice.framework.web.redis.LocalRedisHelper;
import com.ice.framework.web.redis.RedisKey;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author wangwei
 * @Date 2021/12/20 14:27
 */
@Aspect
@Component
@ConditionalOnBean(StringRedisTemplate.class)
public class IdempotentAspect {

    private static Logger logger = LoggerFactory.getLogger(IdempotentAspect.class);


    private static final String KEY_TEMPLATE = "Idempotent";

    private static final String NULL_VALUE = "null";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Pointcut("@annotation(com.ice.framework.web.repeat.Idempotent)")
    public void executeIdempotent() {

    }

    @Around("executeIdempotent()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();
        Idempotent idempotent = (Idempotent) method.getAnnotation(Idempotent.class);
        String methodSignature = this.getMethodSignature(method);
        String keyValue = this.getRequestKeyValue(idempotent, joinPoint.getArgs());
        RedisKey key = RedisKey.valueOf("Idempotent", new String[]{methodSignature, keyValue});
        logger.info("幂等校验：{}", LocalRedisHelper.getRedisResultKey(key));
        boolean result = LocalRedisHelper.setIfAbsent(key, "1", Duration.ofMillis((long) idempotent.time()));
        if (!result) {
            throw new IdempotentException(idempotent.message());
        } else {
            Object t = null;

            try {
                t = joinPoint.proceed();
            } catch (Exception var14) {
                LocalRedisHelper.delete(key);
                throw var14;
            } finally {
                if (!idempotent.force()) {
                    LocalRedisHelper.delete(key);
                }

            }

            return t;
        }
    }

    private String getMethodSignature(Method method) {
        return method.getDeclaringClass().getName() + "." + method.getName();
    }

    private String getRequestKeyValue(Idempotent idempotent, Object[] args) {
        if (idempotent.ignoreAllKey()) {
            return null;
        } else {
            Map<String, String> keyValue = new HashMap();
            this.parseRequestHeader(keyValue, idempotent);
            this.parseMethodArg(keyValue, args, idempotent);
            this.parseRequestParam(keyValue, idempotent);
            return toStringValue(keyValue);
        }
    }

    private void parseRequestHeader(Map<String, String> keyValue, Idempotent annotation) {
        String[] header = annotation.header();
        if (header != null && header.length != 0) {
            HttpServletRequest request = RequestHelper.getRequest();
            if (request != null) {
                String[] var8 = header;
                int var7 = header.length;

                for (int var6 = 0; var6 < var7; ++var6) {
                    String name = var8[var6];
                    putIfAbsent(keyValue, name, request.getHeader(name));
                }

            }
        }
    }

    private void parseMethodArg(Map<String, String> keyValue, Object[] args, Idempotent annotation) {
        if (args != null && args.length != 0) {
            if (annotation.key() != null) {
                Object[] var7 = args;
                int var6 = args.length;

                for (int var5 = 0; var5 < var6; ++var5) {
                    Object bean = var7[var5];
                    this.parseFromBean(keyValue, bean, annotation);
                }

            }
        }
    }

    private void parseRequestParam(Map<String, String> keyValue, Idempotent annotation) {
        HttpServletRequest request = RequestHelper.getRequest();
        if (request != null) {
            if (annotation.key() != null) {
                String[] var7;
                int var6 = (var7 = annotation.key()).length;

                for (int var5 = 0; var5 < var6; ++var5) {
                    String name = var7[var5];
                    putIfAbsent(keyValue, name, request.getParameter(name));
                }

            }
        }
    }

    private void parseFromBean(Map<String, String> keyValue, Object bean, Idempotent annotation) {
        if (!MiscUtil.isBaseType(bean.getClass())) {
            String[] var7;
            int var6 = (var7 = annotation.key()).length;

            for (int var5 = 0; var5 < var6; ++var5) {
                String name = var7[var5];
                putIfAbsent(keyValue, name, ReflectUtil.getFieldValue(bean, name));
            }

        }
    }

    private static void putIfAbsent(Map<String, String> keyValue, String key, Object value) {
        keyValue.putIfAbsent(key, value == null ? null : value.toString());
    }

    private static String toStringValue(Map<String, String> keyValue) {
        if (keyValue.isEmpty()) {
            return "null";
        } else {
            StringBuilder result = new StringBuilder();
            Iterator var3 = keyValue.entrySet().iterator();

            while (var3.hasNext()) {
                Map.Entry<String, String> e = (Map.Entry) var3.next();
                result.append("_").append((String) e.getKey()).append("_").append(MiscUtil.isEmpty(e.getValue()) ? "null" : (String) e.getValue());
            }

            return result.toString();
        }
    }
}
